package com.raven.play.stream.test.javadsl.tutorial;

import akka.actor.PoisonPill;
import akka.actor.TypedActor;
import akka.actor.typed.ActorRef;
import akka.actor.typed.ActorSystem;
import akka.actor.typed.Behavior;
import akka.actor.typed.PreRestart;
import akka.actor.typed.javadsl.*;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import com.raven.play.stream.test.BaseTest;
import lombok.Getter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import scala.concurrent.Await;
import scala.concurrent.duration.Duration;

public class $04AkkaRecap {

    @Getter
    protected static ActorSystem<SimpleActor.Command> system;
    protected static Logger log;

    @BeforeEach
    public void beforeAll() throws Exception {
        system = ActorSystem.create(SimpleActor.create(), "simpleActor");
        log = system.log();
    }

    @AfterEach
    public void afterAll() throws Exception {
        /**
         * 在 Main 方法中执行, 如果 system 没有显式的 terminate, 线程就会一直等待
         * 而在 Junit 测试中, test case 在执行完成之后会直接停止
         * 为了模拟 Main 方法中行为, 这里用了阻塞等待的方式
         */
        Await.result(system.whenTerminated(), Duration.Inf());
    }

    @Test
    public void test() {
        system.tell(new SimpleActor.CreateChild());
        system.tell(new SimpleActor.Message("AHOLA1"));
        system.tell(new SimpleActor.StashThis());
        system.tell(new SimpleActor.Message("AHOLA2"));
        system.tell(new SimpleActor.Message("AHOLA3"));
        system.tell(new SimpleActor.ChangeHandlerNow());
        system.tell(new SimpleActor.Message("AHOLA4"));
        system.tell(new SimpleActor.Message("AHOLA5"));
        system.tell(new SimpleActor.Change());
        system.tell(new SimpleActor.Message("AHOLA6"));

        // 不要忘记在应用结束时终止 Actor 系统
        system.terminate();
    }



    static class SimpleActor extends AbstractBehavior<SimpleActor.Command> {

        private final StashBuffer<Command> stashBuffer;
        private final Logger log;

        private SimpleActor(ActorContext<Command> context, StashBuffer<Command> stashBuffer) {
            super(context);
            this.stashBuffer = stashBuffer;

            /**
             * context.getLog() 区别于使用 LoggingFactory:
             * 上下文感知：日志自动包含关于 Actor 的信息，如 Actor 路径等，这对于调试和跟踪问题非常有用。
             * Akka 配置：日志器遵循 Akka 的配置系统。例如，如果你在 Akka 配置文件中更改了日志级别或日志实现，这将直接反映在使用 getContext().getLog() 获得的日志器上。
             */
            this.log = context.getLog();
        }

        public static Behavior<Command> create() {
            return Behaviors.withStash(10, stashBuffer -> Behaviors.setup(context -> new SimpleActor(context, stashBuffer)));
        }

        @Override
        public Receive<Command> createReceive() {
            return newReceiveBuilder()
                    .onMessage(CreateChild.class, this::OnCreateChild)
                    .onMessage(StashThis.class, this::OnStashThis)
                    .onMessage(ChangeHandlerNow.class, this::OnChangeHandlerNow)
                    .onMessage(Change.class, this::OnChange)
                    .onMessage(Message.class, this::OnMessage)
                    .build();
        }

        private Behavior<Command> OnCreateChild(CreateChild createChild) {
            log.info("Creating childing");
            ActorRef<Command> myChild = getContext().spawn(SimpleActor.create(), "myChild");
            myChild.tell(new Message("hello"));
            return this;
        }

        private Behavior<Command> OnStashThis(StashThis stashThis) {
            stashBuffer.stash(stashThis);
            log.info("Stashed msgs");
            return this;
        }

        private Behavior<Command> OnChangeHandlerNow(ChangeHandlerNow changeHandlerNow) {
            stashBuffer.unstashAll(anotherHandler());
            log.info("UnStashed msgs");
            // 什么时候 return this, 什么时候 return Behaviors?
            // 在 OOP 编程时, 即可以使用 return this, 也可以使用 return Behaviors.same()
            // 在 FP 编程时, 由于函数内部无法使用 this, 只能使用 Behaviors.same()
            // return Behaviors.same();
            return this;
        }

        private Behavior<Command> OnChange(Change change) {
            return anotherHandler();
        }

        private Behavior<Command> OnMessage(Message message) {
            log.info("received: {}", message.text);
            return this;
        }

        private Behavior<Command> anotherHandler() {
            return Behaviors.receive(Command.class)
                    .onMessage(Message.class, msg -> {
                        log.info("Another handler received: {}", msg.text);
                        return Behaviors.same();
                    }).build();
        }

        interface Command {
        }

        public record CreateChild() implements Command {
        }

        public record StashThis() implements Command {
        }

        public record ChangeHandlerNow() implements Command {
        }

        public record Change() implements Command {
        }

        public record Message(String text) implements Command {
        }


    }
}
